namespace AgentJohnson
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text.RegularExpressions;
  using JetBrains.Annotations;
  using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
  using JetBrains.ReSharper.Psi;
  using JetBrains.ReSharper.Psi.Caches;
  using JetBrains.Util;
  using Resharper.Abstractions.Actions;
  using Resharper.Abstractions.CodeDom.Expressions;
  using Resharper.Abstractions.CodeDom.Expressions.LiteralExpressions;
  using Resharper.Abstractions.CodeDom.TypeMembers;
  using Resharper.Abstractions.CodeDom.TypeMembers.Fields.Constants;
  using Resharper.Abstractions.CodeDom.Types.Classes;
  using Resharper.Abstractions.Extensions.StringExtensions;
  using Resharper.Abstractions.PopupMenus;

  /// <summary>Defines the <see cref="IntroduceStringConstant"/> class.</summary>
  [ContextAction("Introduce String Constant [Agent Johnson]", "Creates a constant field from a string literal.")]
  public class IntroduceStringConstant : ContextActionBase
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="IntroduceStringConstant"/> class.</summary>
    /// <param name="provider">The provider.</param>
    public IntroduceStringConstant([NotNull] ICSharpContextActionDataProvider provider) : base(provider)
    {
    }

    #endregion

    /// <summary>
    /// Gets or sets the target class.
    /// </summary>
    /// <value>The target class.</value>
    private Class TargetClass { get; set; }

    /// <summary>
    /// Gets or sets the classes.
    /// </summary>
    /// <value>The classes.</value>
    private List<string> Classes { get; set; }

    #region Methods

    /// <summary>Determines whether this instance can execute the specified action context.</summary>
    /// <param name="context">The action context.</param>
    /// <returns><c>true</c> if this instance can execute the specified action context; otherwise, <c>false</c>.</returns>
    protected override bool CanExecute(CanExecuteContext context)
    {
      this.TargetClass = null;
      this.Classes = null;

      var literalExpression = context.GetSelectedLiteralExpression();
      if (literalExpression == null)
      {
        return false;
      }

      return !string.IsNullOrEmpty(literalExpression.Value);
    }

    /// <summary>Executes the action.</summary>
    /// <param name="context">The execute context.</param>
    protected override void Execute(ExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (this.TargetClass != null)
      {
        this.Execute(context, this.TargetClass);
        return;
      }

      var classNames = context.GetResharperSettingStringList("IntroduceStringConstant.TargetClass");

      var @class = context.GetContainingClass();
      if (@class != null)
      {
        if (classNames.Count == 0)
        {
          this.Execute(context, @class);
          return;
        }

        classNames.Add("<Local> " + @class.Name);
      }

      this.Classes = classNames;
    }

    /// <summary>Executes the action.</summary>
    /// <param name="context">The execute context.</param>
    protected override void PostExecute([NotNull] ExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (this.Classes == null)
      {
        return;
      }

      var menuItems = new List<PopupMenuItem>(this.Classes.Count + 1);
      foreach (var className in this.Classes)
      {
        if (string.IsNullOrEmpty(className))
        {
          continue;
        }

        var item = new PopupMenuItem(className);
        item.Clicked += (sender, args) => this.Execute((PopupMenuItem)sender, context);

        menuItems.Add(item);
      }

      var popupMenu = new PopupMenu("Introduce String Constant");

      popupMenu.Show(menuItems);
    }

    /// <summary>Gets the text.</summary>
    /// <returns>Returns the text.</returns>
    protected override string GetText()
    {
      return "Introduce string constant [Agent Johnson]";
    }

    /// <summary>Executes the specified menu item.</summary>
    /// <param name="menuItem">The menu item.</param>
    /// <param name="context">The context.</param>
    private void Execute([NotNull] PopupMenuItem menuItem, [NotNull] ExecuteContext context)
    {
      if (menuItem == null)
      {
        throw new ArgumentNullException("menuItem");
      }

      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      var className = menuItem.Text;

      Class @class;
      if (className.StartsWith("<Local>"))
      {
        @class = context.GetContainingClass();
      }
      else
      {
        @class = context.GetClassByName(className);
      }

      if (@class == null)
      {
        System.Windows.Forms.MessageBox.Show(string.Format("Class \"{0}\" not found.", className), "Introduce String Constant");
        return;
      }

      this.TargetClass = @class;

      this.Fire(context);
    }

    /// <summary>Gets the identifier.</summary>
    /// <param name="context"></param>
    /// <param name="class">The @class.</param>
    /// <param name="text">The text.</param>
    /// <returns>Returns the identifier.</returns>
    [NotNull]
    private string GetIdentifier([NotNull] ExecuteContext context, [NotNull] Class @class, [NotNull] string text)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (@class == null)
      {
        throw new ArgumentNullException("class");
      }

      if (text == null)
      {
        throw new ArgumentNullException("text");
      }

      var identifier = text;

      if (identifier.StartsWith("\""))
      {
        identifier = identifier.Substring(1);
      }

      if (identifier.EndsWith("\""))
      {
        identifier = identifier.Substring(0, identifier.Length - 1);
      }

      identifier = identifier.RemoveTags();
      identifier = identifier.RemoveControlChars();

      var replaceMode = context.GetResharperSettingInt("IntroduceStringConstant.ReplaceMode", 0);

      if (replaceMode == 0)
      {
        identifier = identifier.Trim().Replace(' ', '_');
        identifier = Regex.Replace(identifier, "\\W", "_");

        while (identifier.IndexOf("__") >= 0)
        {
          identifier = identifier.Replace("__", "_");
        }

        while (identifier.StartsWith("_"))
        {
          identifier = identifier.Substring(1);
        }

        while (identifier.EndsWith("_"))
        {
          identifier = identifier.Substring(0, identifier.Length - 1);
        }
      }
      else
      {
        identifier = identifier.Trim().Replace(" ", string.Empty);
        identifier = Regex.Replace(identifier, "\\W", string.Empty);
      }

      if (string.IsNullOrEmpty(identifier))
      {
        identifier = "Text";
      }

      if (!char.IsLetter(identifier[0]))
      {
        identifier = "Text" + identifier;
      }

      identifier = context.Solution.Naming.GetCanonicalName(context, @class.Language, identifier);

      identifier = identifier.Clip(64);

      var n = 1;
      var result = identifier;
      while (@class.Constants.Any(c => c.Name == result))
      {
        result = identifier + n;
        n++;
      }

      return result;
    }

    /// <summary>Refactors the specified context.</summary>
    /// <param name="context">The context.</param>
    /// <param name="class">The @class.</param>
    private void Execute([NotNull] ExecuteContext context, [NotNull] Class @class)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (@class == null)
      {
        throw new ArgumentNullException("class");
      }

      this.TargetClass = null;
      this.Classes = null;

      var literalExpression = context.GetSelectedLiteralExpression();
      if (literalExpression == null)
      {
        return;
      }

      var text = literalExpression.Value ?? string.Empty;
      var identifier = this.GetIdentifier(context, @class, text);
      var code = string.Format("public const string {0} = \"{1}\";", identifier, text);

      var generateXmlComments = context.GetResharperSettingBool("IntroduceStringConstant.GenerateXmlComments", true);
      if (generateXmlComments)
      {
        code = "/// <summary>" + text + "</summary>\r\n" + code;
      }

      var constant = context.Factory.CreateConstant(code);
      if (constant == null)
      {
        return;
      }

      var anchor = @class.Constants.FirstOrDefault(c => string.Compare(identifier, c.Name, StringComparison.InvariantCultureIgnoreCase) < 0);

      TypeMember typeMember;
      if (anchor != null)
      {
        typeMember = @class.AddMember(constant, anchor);
      }
      else
      {
        typeMember = @class.AddMember(constant);
      }

      if (typeMember != null)
      {
        context.Formatter.Format(typeMember);
      }

      var classFullName = @class.FullName;
      if (!string.IsNullOrEmpty(classFullName))
      {
        identifier = classFullName + "." + identifier;
      }

      var result = literalExpression.ReplaceBy(context.Factory.CreateExpression(identifier));

      context.Formatter.Format(result);
    }

    #endregion
  }
}